import string
import models
import CQutils
import DSHelper
import dalHelper
import random
import logging
import datetime
from google.appengine.api import memcache
from CQutils import EPositionStatus
from CQutils import EQueueStatus
from google.appengine.ext import db

def queue_size_key(queue_cqid):
    return 'queue?%s:queue_size' % (queue_cqid)

def queue_pos_key(queue_cqid, user_cqid):
    return 'queue?%s&user?%s:position' % (queue_cqid, user_cqid)

def queue_invalidation_key(queue_cqid):
    return 'queue?%s:invalid' % (queue_cqid)

def pos_invalidation_key(queue_cqid, user_cqid):
    return 'queue?%s&user?%s:invalid' % (queue_cqid, user_cqid)
    
def generate_random_id(size=5):
    return ''.join(random.choice(string.ascii_uppercase + string.digits) for x in range(size))    

######## QUEUE OPS ##################   
 
def create_queue(admin, queue_name, queue_cqid, queue_type=0, avg_time_min=0, queue_img=None):
    return create_queue_by_id(admin.user_id(), queue_name, queue_cqid, queue_type, avg_time_min, queue_img)

def create_queue_by_id(admin_id, queue_name, queue_cqid, queue_type=0, avg_time_min=0, queue_img=None):
    try:
        int(avg_time_min)
    except:
        return -2  # parameters error
    existed_queues = DSHelper.get_queue_from_name_by_id(queue_name, admin_id)
    if existed_queues is not None:
        for q in existed_queues: 
            if q.status != EQueueStatus.DELETED:
                logging.warn("queue name already exists and not deleted")
                return -1
    ds_admin = DSHelper.get_admin_by_googleid(admin_id)
    if (ds_admin == None):
        ds_admin = DSHelper.create_admin_by_id(admin_id)  # was create_admin(admin_id) anc caused error - almog.
    q = models.DSQueue(parent=ds_admin, name=queue_name, queue_cqid=queue_cqid, status=0, place=0, type=queue_type, avg_time_sec=avg_time_min * 60, image=db.Blob(queue_img))
    q.put()  
    memcache.Client().set("queue?" + queue_cqid + ":obj", q) 
    memcache.Client().set(queue_size_key(queue_cqid), 0)
    return 0

def stop_queue(queue_cqid):
    queue = DSHelper.get_queue_by_cqid(queue_cqid)
    memcache.Client().set(queue_invalidation_key(queue_cqid), generate_random_id(10)) 
    queue.status = EQueueStatus.TERMINATED
    queue.put()   
    memcache.Client().set("queue?" + queue_cqid + ":obj", queue) 
    
def delete_queue(queue_cqid):
    memcache.Client().delete(queue_size_key(queue_cqid))    
    memcache.Client().set(queue_invalidation_key(queue_cqid), generate_random_id(10)) 
    queue = DSHelper.get_queue_by_cqid(queue_cqid)
    queue.status = EQueueStatus.DELETED
    queue.put() 
    memcache.Client().set("queue?" + queue_cqid + ":obj", queue) 

def resume_queue(queue_cqid):
    queue = DSHelper.get_queue_by_cqid(queue_cqid)
    memcache.Client().set(queue_invalidation_key(queue_cqid), generate_random_id(10)) 
    queue.status = EQueueStatus.RUNNING
    queue.put()   
    memcache.Client().set("queue?" + queue_cqid + ":obj", queue)

def get_queue_image(queue_cqid):
    queue = DSHelper.get_queue_by_cqid(queue_cqid)
    image = queue.image
    return image
     
def set_queue_image(queue_cqid, queue_image):
    queue = DSHelper.get_queue_by_cqid(queue_cqid)
    if (queue_image):
        queue.image = db.Blob(queue_image)
    queue.put()
    memcache.Client().set("queue?" + queue_cqid + ":obj", queue)

def del_queue_image(queue_cqid):
    queue = DSHelper.get_queue_by_cqid(queue_cqid)
    queue.image = None
    queue.put()
    memcache.Client().set("queue?" + queue_cqid + ":obj", queue) 
  
    
########## QUEUE STATUS #################

def advance_queue(queue_cqid, ext=None): 
    queue = DSHelper.get_queue_by_cqid(queue_cqid) 
    dalHelper._advance_current_user(queue, ext)     
    queue_positions = models.DSPosition.all().ancestor(queue).filter("status =", EPositionStatus.INLINE).order("join_time")
    next_pos = queue_positions.get()
    if next_pos is None:
        return None
    next_users = get_next_users(queue_cqid, 2)
    memcache.Client().decr(queue_size_key(queue_cqid))  
    memcache.Client().set(queue_invalidation_key(queue_cqid), generate_random_id(10))  
    next_pos.status = EPositionStatus.IN_PROCESS
    next_pos.processed_time = datetime.datetime.now()
    if ext is not None:
        next_pos.ext = str(ext)
    next_pos.put()    
    memcache.Client().set("position?" + next_pos.pos_cqid + ":obj", next_pos)
    CQutils.notify_next_users(next_users, ext)
    return next_pos.user_ref.user_cqid

def get_queue_type(queue_cqid):
    queue = DSHelper.get_queue_by_cqid(queue_cqid)
    if queue == None:
        return -1
    return queue.type 

def get_inservice_users(queue_cqid):
    queue = DSHelper.get_queue_by_cqid(queue_cqid)
    clients = []
    if queue is not None:
        queue_inservice_positions = models.DSPosition.all().ancestor(queue).filter("status =", EPositionStatus.IN_PROCESS).order("-join_time")        
        for pos in queue_inservice_positions.run() :
            clients.append((pos.user_ref.user_cqid, pos.ext))    
    return clients  

def get_next_users(queue_cqid, count):
    ret_arr = []
    queue = DSHelper.get_queue_by_cqid(queue_cqid)
    if queue is None:
        return ret_arr
    queue_invalidation_id = memcache.Client().get(queue_invalidation_key(queue.queue_cqid))
    if queue_invalidation_id == memcache.Client().get("queue_next_invalidation_key:" + queue_cqid):
        next_users = memcache.Client().get("queue?" + queue_cqid + ":next")
        if next_users is not None:
            return next_users[:min(len(next_users), count)]
    memcache.Client().set("queue_next_invalidation_key:" + queue_cqid, queue_invalidation_id)
    queue_wainting_pos = models.DSPosition.all().ancestor(queue).filter("status =", EPositionStatus.INLINE).order("join_time")
    for pos in queue_wainting_pos.run(limit=count):      
        ret_arr.append(pos.user_ref.user_cqid)
    memcache.Client().set("queue?" + queue_cqid + ":next", ret_arr)
    return ret_arr

def get_queue_size(queue_cqid):
    queue_size = memcache.Client().get(queue_size_key(queue_cqid))
    if queue_size is not None:
        return queue_size
    queue = DSHelper.get_queue_by_cqid(queue_cqid) 
    if queue == None:
        return -1
    queue_waiting_users = models.DSPosition.all().ancestor(queue).filter("status =", EPositionStatus.INLINE)
    size = queue_waiting_users.count()   
    memcache.Client().set(queue_size_key(queue_cqid), size)    
    return size

def get_queue_name(queue_cqid):
    queue = DSHelper.get_queue_by_cqid(queue_cqid)
    if queue == None:
        return ""
    return queue.name

def get_queue_status(queue_cqid):
    queue = DSHelper.get_queue_by_cqid(queue_cqid)
    if queue == None:
        return ""
    return queue.status

def get_all_users_in_queue(queue_cqid):
    queue = DSHelper.get_queue_by_cqid(queue_cqid)
    queue_inline_pos = models.DSPosition.all().ancestor(queue).filter("status =", EPositionStatus.INLINE)
    ret_arr = []
    for pos in queue_inline_pos.run():      
        ret_arr.append(pos.user_ref.user_cqid)
    return ret_arr

def get_admin_queue(queue_cqid):
    queue = DSHelper.get_queue_by_cqid(queue_cqid)
    admin = queue.parent()
    if admin == None:
        return None
    return admin.google_id

########## USERS OPS ######################

def get_user_details(user_cqid):
    user = DSHelper.get_user_from_userid(user_cqid)
    if user is not None:
        return  {"name": user.name, "phone" : user.phone}
    return None

def create_user(details): 
    user = DSHelper.create_user(details)  
    return user.user_cqid

def join_queue(user_cqid, queue_cqid):
    queue = DSHelper.get_queue_by_cqid(queue_cqid)
    user = DSHelper.get_user_from_userid(user_cqid)
    if queue is None or user is None:
        return
    user_positions = DSHelper.get_positions_from_userid(user_cqid)
    pos = DSHelper.get_position_from_posid(get_user_pos_in_queue(user_cqid, queue_cqid))
    if pos is None:  # the user in not in the queue
        pos_cqid = DSHelper.generate_random_id(15)
        pos = models.DSPosition(parent=queue, pos_cqid=pos_cqid, user_ref=user, status=EPositionStatus.INLINE)  
        user_positions.append(pos.pos_cqid)
        memcache.Client().set("user?" + user_cqid + ":positions", user_positions) 
    else:  # the user is already in the queue not in the queue - rejoin
        # user_positions = [p for p in user_positions if p.parent().queue_cqid != queue_cqid]
        pos.status = EPositionStatus.INLINE
        pos.join_time = datetime.datetime.now()
    pos.put() 
    memcache.Client().set(queue_invalidation_key(queue_cqid), generate_random_id(10))
    memcache.Client().set("position?" + pos.pos_cqid + ":obj", pos)
    memcache.Client().incr(queue_size_key(queue_cqid))    
    return user.user_cqid

def get_position_status(pos_cqid):
    position = DSHelper.get_position_from_posid(pos_cqid)
    if position == None:
        return CQutils.EPositionStatus.LEFT;
    return position.status
   
def leave_queue(pos_cqid):
    queue = DSHelper.get_queue_from_posid(pos_cqid)
    if queue is None:
        return
    pos = DSHelper.get_position_from_posid(pos_cqid)
    pos.status = EPositionStatus.LEFT
    pos.put()
    memcache.Client().decr(queue_size_key(queue.queue_cqid)) 
    memcache.Client().set(queue_invalidation_key(queue.queue_cqid), generate_random_id(10)) 
    memcache.Client().set("position?" + pos_cqid + ":obj", pos)  

def get_pos_number(pos_cqid):
    queue = DSHelper.get_queue_from_posid(pos_cqid)
    if queue is None:
        return -4
    queue_invalidation_id = memcache.Client().get(queue_invalidation_key(queue.queue_cqid))
    if queue_invalidation_id == memcache.Client().get(pos_invalidation_key(queue.queue_cqid, pos_cqid)):
        pos_num = memcache.Client().get(queue_pos_key(queue.queue_cqid, pos_cqid))
        if pos_num is not None:
            return pos_num
    memcache.Client().set(pos_invalidation_key(queue.queue_cqid, pos_cqid), queue_invalidation_id)   
    pos = DSHelper.get_position_from_posid(pos_cqid)
    if pos == None:
        pos_num = -4  # # error in database (invalid pos)
    if len([u_cqid for (u_cqid, ext) in get_inservice_users(queue.queue_cqid) if u_cqid == pos.user_ref.user_cqid]) != 0:
        pos_num = 0
    elif queue.status != CQutils.EQueueStatus.RUNNING:
        pos_num = -2  # queue has been deleted or closed 
    else:
        if pos.status != EPositionStatus.INLINE:
            pos_num = -1  # user is not in the line (already processed) 
        else:
            queue_positions = models.DSPosition.all().ancestor(queue).filter("status =", EPositionStatus.INLINE).filter("join_time <= ", pos.join_time) 
            pos_num = queue_positions.count()
    memcache.Client().set(queue_pos_key(queue.queue_cqid, pos_cqid), pos_num)
    return pos_num

def get_user_pos_in_queue(user_cqid, queue_cqid):
    positions = DSHelper.get_positions_from_userid(user_cqid)
    for pos in positions:
        queue = DSHelper.get_queue_from_pos(pos)
        if queue is not None and queue.queue_cqid == queue_cqid:
            return pos.pos_cqid
    return None

def get_queue_by_position(pos_cqid):
    queue = DSHelper.get_queue_from_posid(pos_cqid)
    if queue is None:
        return ""
    return queue.queue_cqid

def get_user_positions(user_cqid):
    positions = DSHelper.get_positions_from_userid(user_cqid)
    ret_arr = []
    for pos in positions:
        ret_arr.append(pos.pos_cqid)
    return ret_arr

def get_all_queues_from_admin(admin):
    return get_all_queues_from_admin_by_id(admin.user_id())

def get_all_queues_from_admin_by_id(admin_id):
    retval = []
    admin = DSHelper.get_admin_by_googleid(admin_id)
    if admin == None:
        return retval
    admin_queues = models.DSQueue.all().ancestor(admin).filter("status !=", EQueueStatus.DELETED)
    for q in admin_queues:
        retval.append(q.queue_cqid)
    return retval

def is_valid_user(user_cqid):
    return DSHelper.get_user_from_userid(user_cqid) != None

def is_valid_queue(queue_cqid):
    return DSHelper.get_queue_by_cqid(queue_cqid) != None

######## STATISTICS #########

def get_queue_statistics(queue_cqid):
    queue = DSHelper.get_queue_by_cqid(queue_cqid)
    if queue is None:
        return (0, 0, 0, 0)
    avg_time_threshold = int(queue.avg_time_sec)
    deltas = dalHelper._get_inqueue_detla_times(queue_cqid)  
    if len(deltas) < 2:
        return (avg_time_threshold, avg_time_threshold, avg_time_threshold, avg_time_threshold)   
    avg_time = dalHelper._calculate_turn_avarage(deltas, avg_time_threshold)
    if avg_time is 0:
        return (0, 0, 0, 0)
    last_deltas = deltas[-5:]
    last_avg = sum(last_deltas) / len(last_deltas)
    total_avg = avg_time
    min_delta = min(deltas)
    max_delta = max(deltas)
    return(last_avg, total_avg, min_delta, max_delta)

def get_last_service_delta(queue_cqid):
    if len(get_inservice_users(queue_cqid)) == 0:
        return -1
    elapsed_time = datetime.datetime.now() - dalHelper._get_last_service_time(queue_cqid)
    return elapsed_time.total_seconds()


